package cn.emailChat.ai.service.util;

import org.jsoup.Jsoup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jakarta.mail.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * @Description 工具类：提取邮件正文并记录命中的内容类型
 * @Author susu
 * @Date 2025/8/18
 */
public class MailTextExtractor {
    private static final Logger log = LoggerFactory.getLogger(MailTextExtractor.class);

    public static String extractBestText(Message message) throws MessagingException, IOException {
        String topCt = safeCt(message.getContentType());
        Object content = message.getContent();
        String contentClass = (content == null) ? "null" : content.getClass().getName();
        log.info("[MailTextExtractor] message contentType={}, contentClass={}", topCt, contentClass);

        String text = extractFromPart(message).trim();
        if (text.isEmpty()) {
            log.info("[MailTextExtractor] 提取结果为空字符串");
        }
        return text;
    }

    private static String extractFromPart(Part part) throws MessagingException, IOException {
        String ct = safeCt(part.getContentType());

        if (part.isMimeType("text/plain")) {
            log.info("[MailTextExtractor] 命中: text/plain (ct={})", ct);
            return readTextPart(part);
        }
        if (part.isMimeType("text/html")) {
            log.info("[MailTextExtractor] 命中: text/html (ct={}) -> 转纯文本", ct);
            return htmlToText(readTextPart(part));
        }
        if (part.isMimeType("text/calendar") || part.isMimeType("text/rtf")) {
            log.info("[MailTextExtractor] 命中: {} (ct={}) -> 按文本读取",
                    part.isMimeType("text/calendar") ? "text/calendar" : "text/rtf", ct);
            return readTextPart(part);
        }

        if (part.isMimeType("multipart/*")) {
            log.debug("[MailTextExtractor] 进入 multipart (ct={})", ct);
            Object c = part.getContent();
            if (c instanceof Multipart) {
                String text = handleMultipart((Multipart) c);
                if (!text.isEmpty()) return text;
            }
        }

        if (part.isMimeType("message/rfc822")) {
            log.debug("[MailTextExtractor] 进入嵌套 message/rfc822 (ct={})", ct);
            Object c = part.getContent();
            if (c instanceof Message) {
                return extractFromPart((Message) c);
            }
        }

        String unknown = readUnknownAsText(part);
        if (!unknown.isEmpty()) {
            log.info("[MailTextExtractor] 未知类型按文本读取成功 (ct={})", ct);
        } else {
            log.info("[MailTextExtractor] 未知类型像二进制或为空 (ct={})", ct);
        }
        return unknown;
    }

    private static String handleMultipart(Multipart multipart) throws MessagingException, IOException {
        String fallbackHtml = null;

        for (int i = 0; i < multipart.getCount(); i++) {
            BodyPart bp = multipart.getBodyPart(i);
            String ct = safeCt(bp.getContentType());
            if (Part.ATTACHMENT.equalsIgnoreCase(bp.getDisposition())) {
                log.debug("[MailTextExtractor] 跳过附件 part#{} ct={}", i, ct);
                continue;
            }
            if (bp.isMimeType("text/plain")) {
                log.info("[MailTextExtractor] multipart 命中 text/plain 于 part#{} (ct={})", i, ct);
                String s = readTextPart(bp);
                if (!s.trim().isEmpty()) return s;
            }
        }

        for (int i = 0; i < multipart.getCount(); i++) {
            BodyPart bp = multipart.getBodyPart(i);
            String ct = safeCt(bp.getContentType());
            if (Part.ATTACHMENT.equalsIgnoreCase(bp.getDisposition())) continue;

            if (bp.isMimeType("text/html")) {
                log.info("[MailTextExtractor] multipart 命中 text/html 于 part#{} (ct={}) -> 转纯文本", i, ct);
                fallbackHtml = readTextPart(bp);
                if (!fallbackHtml.trim().isEmpty()) {
                    return htmlToText(fallbackHtml);
                }
            }
        }

        for (int i = 0; i < multipart.getCount(); i++) {
            BodyPart bp = multipart.getBodyPart(i);
            String ct = safeCt(bp.getContentType());
            if (Part.ATTACHMENT.equalsIgnoreCase(bp.getDisposition())) continue;

            Object c = bp.getContent();
            if (c instanceof Multipart) {
                log.debug("[MailTextExtractor] 深入嵌套 multipart 于 part#{} (ct={})", i, ct);
                String nested = handleMultipart((Multipart) c);
                if (!nested.trim().isEmpty()) return nested;
            } else if (bp.isMimeType("message/rfc822")) {
                log.debug("[MailTextExtractor] 深入嵌套 message/rfc822 于 part#{} (ct={})", i, ct);
                Object mc = bp.getContent();
                if (mc instanceof Message) {
                    String nested = extractFromPart((Message) mc);
                    if (!nested.trim().isEmpty()) return nested;
                }
            }
        }

        log.debug("[MailTextExtractor] multipart 未找到可用文本");
        return "";
    }

    private static String readTextPart(Part part) throws IOException, MessagingException {
        String ct = safeCt(part.getContentType());
        String charset = getCharsetFromContentType(ct);
        byte[] bytes = toByteArray(part.getInputStream());
        String s = decodeBytes(bytes, charset);
        log.debug("[MailTextExtractor] readTextPart 完成 (ct={}, charset={}, bytes={})", ct, charset, bytes.length);
        return s;
    }

    private static String readUnknownAsText(Part part) throws IOException, MessagingException {
        String ct = safeCt(part.getContentType());
        String charset = getCharsetFromContentType(ct);
        byte[] bytes = toByteArray(part.getInputStream());
        String s = decodeBytes(bytes, charset);
        boolean binary = isMostlyBinary(s);
        log.debug("[MailTextExtractor] readUnknownAsText 完成 (ct={}, charset={}, bytes={}, binaryLikely={})",
                ct, charset, bytes.length, binary);
        return binary ? "" : s;
    }

    private static String getCharsetFromContentType(String contentType) {
        if (contentType == null) return null;
        try {
            String[] parts = contentType.split(";");
            for (String p : parts) {
                String kv = p.trim().toLowerCase();
                if (kv.startsWith("charset=")) {
                    String cs = kv.substring("charset=".length()).trim();
                    cs = cs.replace("\"", "").replace("'", "");
                    return cs;
                }
            }
        } catch (Exception ignored) {}
        return null;
    }

    private static byte[] toByteArray(java.io.InputStream is) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        byte[] data = new byte[4096];
        int n;
        while ((n = is.read(data)) != -1) {
            buffer.write(data, 0, n);
        }
        buffer.flush();
        return buffer.toByteArray();
    }

    private static String decodeBytes(byte[] bytes, String charset) {
        try {
            if (charset != null && !charset.isEmpty()) {
                return new String(bytes, charset);
            }
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (Exception e1) {
            try {
                return new String(bytes, "GBK");
            } catch (Exception e2) {
                try {
                    return new String(bytes, StandardCharsets.ISO_8859_1);
                } catch (Exception e3) {
                    return new String(bytes);
                }
            }
        }
    }

    private static String htmlToText(String html) {
        return Jsoup.parse(html).text();
    }

    private static boolean isMostlyBinary(String s) {
        if (s == null || s.isEmpty()) return false;
        int control = 0, len = Math.min(s.length(), 200);
        for (int i = 0; i < len; i++) {
            char ch = s.charAt(i);
            if (Character.isISOControl(ch) && !Character.isWhitespace(ch)) {
                control++;
            }
        }
        return control > len / 10;
    }

    private static String safeCt(String ct) {
        return (ct == null) ? "null" : ct;
    }
}
